/*
======================================================================
envelope.c

Envelope functions for an LWO2 reader.

Ernie Wright  16 Nov 00
======================================================================
*/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "lwo2.h"


/*
======================================================================
lwFreeEnvelope()

Free the memory used by an lwEnvelope.
======================================================================
*/
void lwFreeEnvelope(lwEnvelope * env)
{
	if(env)
	{
		if(env->name)
			free(env->name);
		lwListFree(env->key, free);
		lwListFree(env->cfilter, (void *)lwFreePlugin);
		free(env);
	}
}


static int compare_keys(lwKey * k1, lwKey * k2)
{
	return k1->time > k2->time ? 1 : k1->time < k2->time ? -1 : 0;
}


/*
======================================================================
lwGetEnvelope()

Read an ENVL chunk from an LWO2 file.
======================================================================
*/
lwEnvelope     *lwGetEnvelope(FILE * fp, int cksize)
{
	lwEnvelope     *env;
	lwKey          *key = NULL;
	lwPlugin       *plug;
	unsigned int    id;
	unsigned short  sz;
	float           f[4];
	int             i, nparams, pos, rlen;


	/* allocate the Envelope structure */
	env = calloc(1, sizeof(lwEnvelope));
	if(!env)
		goto Fail;

	/* remember where we started */
	set_flen(0);
	pos = ftell(fp);

	/* index */
	env->index = getVX(fp);

	/* first subchunk header */
	id = getU4(fp);
	sz = getU2(fp);
	if(0 > get_flen())
		goto Fail;

	/* process subchunks as they're encountered */
	while(1)
	{
		sz += sz & 1;
		set_flen(0);

		switch (id)
		{
			case ID_TYPE:
				env->type = getU2(fp);
				break;

			case ID_NAME:
				env->name = getS0(fp);
				break;

			case ID_PRE:
				env->behavior[0] = getU2(fp);
				break;

			case ID_POST:
				env->behavior[1] = getU2(fp);
				break;

			case ID_KEY:
				key = calloc(1, sizeof(lwKey));
				if(!key)
					goto Fail;
				key->time = getF4(fp);
				key->value = getF4(fp);
				lwListInsert((void *)&env->key, key, (void *)compare_keys);
				env->nkeys++;
				break;

			case ID_SPAN:
				if(!key)
					goto Fail;
				key->shape = getU4(fp);

				nparams = (sz - 4) / 4;
				if(nparams > 4)
					nparams = 4;
				for(i = 0; i < nparams; i++)
					f[i] = getF4(fp);

				switch (key->shape)
				{
					case ID_TCB:
						key->tension = f[0];
						key->continuity = f[1];
						key->bias = f[2];
						break;

					case ID_BEZI:
					case ID_HERM:
					case ID_BEZ2:
						for(i = 0; i < nparams; i++)
							key->param[i] = f[i];
						break;
				}
				break;

			case ID_CHAN:
				plug = calloc(1, sizeof(lwPlugin));
				if(!plug)
					goto Fail;

				plug->name = getS0(fp);
				plug->flags = getU2(fp);
				plug->data = getbytes(fp, sz - get_flen());

				lwListAdd((void *)&env->cfilter, plug);
				env->ncfilters++;
				break;

			default:
				break;
		}

		/* error while reading current subchunk? */
		rlen = get_flen();
		if(rlen < 0 || rlen > sz)
			goto Fail;

		/* skip unread parts of the current subchunk */
		if(rlen < sz)
			fseek(fp, sz - rlen, SEEK_CUR);

		/* end of the ENVL chunk? */
		rlen = ftell(fp) - pos;
		if(cksize < rlen)
			goto Fail;
		if(cksize == rlen)
			break;

		/* get the next subchunk header */
		set_flen(0);
		id = getU4(fp);
		sz = getU2(fp);
		if(6 != get_flen())
			goto Fail;
	}

	return env;

  Fail:
	lwFreeEnvelope(env);
	return NULL;
}


/*
======================================================================
lwFindEnvelope()

Returns an lwEnvelope pointer, given an envelope index.
======================================================================
*/
lwEnvelope     *lwFindEnvelope(lwEnvelope * list, int index)
{
	lwEnvelope     *env;

	env = list;
	while(env)
	{
		if(env->index == index)
			break;
		env = env->next;
	}
	return env;
}


/*
======================================================================
range()

Given the value v of a periodic function, returns the equivalent value
v2 in the principal interval [lo, hi].  If i isn't NULL, it receives
the number of wavelengths between v and v2.

   v2 = v - i * (hi - lo)

For example, range( 3 pi, 0, 2 pi, i ) returns pi, with i = 1.
======================================================================
*/
static float range(float v, float lo, float hi, int *i)
{
	float           v2, r = hi - lo;

	if(r == 0.0)
	{
		if(i)
			*i = 0;
		return lo;
	}

	v2 = lo + v - r * (float)floor((double)v / r);
	if(i)
		*i = -(int)((v2 - v) / r + (v2 > v ? 0.5 : -0.5));

	return v2;
}


/*
======================================================================
hermite()

Calculate the Hermite coefficients.
======================================================================
*/
static void hermite(float t, float *h1, float *h2, float *h3, float *h4)
{
	float           t2, t3;

	t2 = t * t;
	t3 = t * t2;

	*h2 = 3.0f * t2 - t3 - t3;
	*h1 = 1.0f - *h2;
	*h4 = t3 - t2;
	*h3 = *h4 - t2 + t;
}


/*
======================================================================
bezier()

Interpolate the value of a 1D Bezier curve.
======================================================================
*/
static float bezier(float x0, float x1, float x2, float x3, float t)
{
	float           a, b, c, t2, t3;

	t2 = t * t;
	t3 = t2 * t;

	c = 3.0f * (x1 - x0);
	b = 3.0f * (x2 - x1) - c;
	a = x3 - x0 - c - b;

	return a * t3 + b * t2 + c * t + x0;
}


/*
======================================================================
bez2_time()

Find the t for which bezier() returns the input time.  The handle
endpoints of a BEZ2 curve represent the control points, and these have
(time, value) coordinates, so time is used as both a coordinate and a
parameter for this curve type.
======================================================================
*/
static float bez2_time(float x0, float x1, float x2, float x3, float time, float *t0, float *t1)
{
	float           v, t;

	t = *t0 + (*t1 - *t0) * 0.5f;
	v = bezier(x0, x1, x2, x3, t);
	if(fabs(time - v) > .0001f)
	{
		if(v > time)
			*t1 = t;
		else
			*t0 = t;
		return bez2_time(x0, x1, x2, x3, time, t0, t1);
	}
	else
		return t;
}


/*
======================================================================
bez2()

Interpolate the value of a BEZ2 curve.
======================================================================
*/
static float bez2(lwKey * key0, lwKey * key1, float time)
{
	float           x, y, t, t0 = 0.0f, t1 = 1.0f;

	if(key0->shape == ID_BEZ2)
		x = key0->time + key0->param[2];
	else
		x = key0->time + (key1->time - key0->time) / 3.0f;

	t = bez2_time(key0->time, x, key1->time + key1->param[0], key1->time, time, &t0, &t1);

	if(key0->shape == ID_BEZ2)
		y = key0->value + key0->param[3];
	else
		y = key0->value + key0->param[1] / 3.0f;

	return bezier(key0->value, y, key1->param[1] + key1->value, key1->value, t);
}


/*
======================================================================
outgoing()

Return the outgoing tangent to the curve at key0.  The value returned
for the BEZ2 case is used when extrapolating a linear pre behavior and
when interpolating a non-BEZ2 span.
======================================================================
*/
static float outgoing(lwKey * key0, lwKey * key1)
{
	float           a, b, d, t, out;

	switch (key0->shape)
	{
		case ID_TCB:
			a = (1.0f - key0->tension) * (1.0f + key0->continuity) * (1.0f + key0->bias);
			b = (1.0f - key0->tension) * (1.0f - key0->continuity) * (1.0f - key0->bias);
			d = key1->value - key0->value;

			if(key0->prev)
			{
				t = (key1->time - key0->time) / (key1->time - key0->prev->time);
				out = t * (a * (key0->value - key0->prev->value) + b * d);
			}
			else
				out = b * d;
			break;

		case ID_LINE:
			d = key1->value - key0->value;
			if(key0->prev)
			{
				t = (key1->time - key0->time) / (key1->time - key0->prev->time);
				out = t * (key0->value - key0->prev->value + d);
			}
			else
				out = d;
			break;

		case ID_BEZI:
		case ID_HERM:
			out = key0->param[1];
			if(key0->prev)
				out *= (key1->time - key0->time) / (key1->time - key0->prev->time);
			break;

		case ID_BEZ2:
			out = key0->param[3] * (key1->time - key0->time);
			if(fabs(key0->param[2]) > 1e-5f)
				out /= key0->param[2];
			else
				out *= 1e5f;
			break;

		case ID_STEP:
		default:
			out = 0.0f;
			break;
	}

	return out;
}


/*
======================================================================
incoming()

Return the incoming tangent to the curve at key1.  The value returned
for the BEZ2 case is used when extrapolating a linear post behavior.
======================================================================
*/
static float incoming(lwKey * key0, lwKey * key1)
{
	float           a, b, d, t, in;

	switch (key1->shape)
	{
		case ID_LINE:
			d = key1->value - key0->value;
			if(key1->next)
			{
				t = (key1->time - key0->time) / (key1->next->time - key0->time);
				in = t * (key1->next->value - key1->value + d);
			}
			else
				in = d;
			break;

		case ID_TCB:
			a = (1.0f - key1->tension) * (1.0f - key1->continuity) * (1.0f + key1->bias);
			b = (1.0f - key1->tension) * (1.0f + key1->continuity) * (1.0f - key1->bias);
			d = key1->value - key0->value;

			if(key1->next)
			{
				t = (key1->time - key0->time) / (key1->next->time - key0->time);
				in = t * (b * (key1->next->value - key1->value) + a * d);
			}
			else
				in = a * d;
			break;

		case ID_BEZI:
		case ID_HERM:
			in = key1->param[0];
			if(key1->next)
				in *= (key1->time - key0->time) / (key1->next->time - key0->time);
			break;
			return in;

		case ID_BEZ2:
			in = key1->param[1] * (key1->time - key0->time);
			if(fabs(key1->param[0]) > 1e-5f)
				in /= key1->param[0];
			else
				in *= 1e5f;
			break;

		case ID_STEP:
		default:
			in = 0.0f;
			break;
	}

	return in;
}


/*
======================================================================
evalEnvelope()

Given a list of keys and a time, returns the interpolated value of the
envelope at that time.
======================================================================
*/
float evalEnvelope(lwEnvelope * env, float time)
{
	lwKey          *key0, *key1, *skey, *ekey;
	float           t, h1, h2, h3, h4, in, out, offset = 0.0f;
	int             noff;


	/* if there's no key, the value is 0 */
	if(env->nkeys == 0)
		return 0.0f;

	/* if there's only one key, the value is constant */
	if(env->nkeys == 1)
		return env->key->value;

	/* find the first and last keys */
	skey = ekey = env->key;
	while(ekey->next)
		ekey = ekey->next;

	/* use pre-behavior if time is before first key time */
	if(time < skey->time)
	{
		switch (env->behavior[0])
		{
			case BEH_RESET:
				return 0.0f;

			case BEH_CONSTANT:
				return skey->value;

			case BEH_REPEAT:
				time = range(time, skey->time, ekey->time, NULL);
				break;

			case BEH_OSCILLATE:
				time = range(time, skey->time, ekey->time, &noff);
				if(noff % 2)
					time = ekey->time - skey->time - time;
				break;

			case BEH_OFFSET:
				time = range(time, skey->time, ekey->time, &noff);
				offset = noff * (ekey->value - skey->value);
				break;

			case BEH_LINEAR:
				out = outgoing(skey, skey->next) / (skey->next->time - skey->time);
				return out * (time - skey->time) + skey->value;
		}
	}

	/* use post-behavior if time is after last key time */
	else if(time > ekey->time)
	{
		switch (env->behavior[1])
		{
			case BEH_RESET:
				return 0.0f;

			case BEH_CONSTANT:
				return ekey->value;

			case BEH_REPEAT:
				time = range(time, skey->time, ekey->time, NULL);
				break;

			case BEH_OSCILLATE:
				time = range(time, skey->time, ekey->time, &noff);
				if(noff % 2)
					time = ekey->time - skey->time - time;
				break;

			case BEH_OFFSET:
				time = range(time, skey->time, ekey->time, &noff);
				offset = noff * (ekey->value - skey->value);
				break;

			case BEH_LINEAR:
				in = incoming(ekey->prev, ekey) / (ekey->time - ekey->prev->time);
				return in * (time - ekey->time) + ekey->value;
		}
	}

	/* get the endpoints of the interval being evaluated */
	key0 = env->key;
	while(time > key0->next->time)
		key0 = key0->next;
	key1 = key0->next;

	/* check for singularities first */
	if(time == key0->time)
		return key0->value + offset;
	else if(time == key1->time)
		return key1->value + offset;

	/* get interval length, time in [0, 1] */
	t = (time - key0->time) / (key1->time - key0->time);

	/* interpolate */
	switch (key1->shape)
	{
		case ID_TCB:
		case ID_BEZI:
		case ID_HERM:
			out = outgoing(key0, key1);
			in = incoming(key0, key1);
			hermite(t, &h1, &h2, &h3, &h4);
			return h1 * key0->value + h2 * key1->value + h3 * out + h4 * in + offset;

		case ID_BEZ2:
			return bez2(key0, key1, time) + offset;

		case ID_LINE:
			return key0->value + t * (key1->value - key0->value) + offset;

		case ID_STEP:
			return key0->value + offset;

		default:
			return offset;
	}
}
